package testApplets;

import marchingCubes.MCubeEngineLowMemory;
import peasy.PeasyCam;
import processing.core.PApplet;
import processing.core.PGraphics3D;
import processing.core.PImage;
import simpleGeom.OpenGLMesh;

public class TuringApplet extends PApplet {
	int n, levels;
	float[] grid;
	float[] diffusionLeft,diffusionRight, blurBuffer, variation;
	float[] bestVariation;
	int[] bestLevel;
	boolean[] direction;
	float[] stepSizes;
	int[] radii;

	PImage buffer;

	int nX=40;
	int nY=400;
	int nZ=200;
	float[] values3D=new float[nX*nY*nZ];
	PeasyCam cam;
	boolean flagExport=false;
	public void setup() {
	  size(1000, 800, P3D);
	  cam=new PeasyCam(this,0,nY*0.5f,nZ*0.5f,500);
	  // relates to the complexity (how many blur levels)
	  float base = random(1.5f, 2.4f); // should be between 1.3 and 3
	   
	  // these values affect how fast the image changes
	  float stepScale = random(.006f, .011f);
	  float stepOffset =  random(.007f, .012f);
	 
	  // allocate space
	  n = nY * nZ;
	  levels = (int) (log(nY) / log(base));
	  
	  radii = new int[levels];
	  stepSizes = new float[levels];
	  grid = new float[n];
	  diffusionLeft = new float[n];
	  diffusionRight = new float[n];
	  blurBuffer = new float[n];
	  variation = new float[n];
	  bestVariation = new float[n];
	  bestLevel = new int[n];
	  direction = new boolean[n];
	  buffer = createImage(nY, nZ, RGB);
	 
	  // determines the shape of the patterns
	  for (int i = 0; i < levels; i++) {
	    int radius = (int) pow(base, i);
	    radii[i] = radius;
	    stepSizes[i] = log(radius) * stepScale + stepOffset;
	  }
	 
	  // initialize the grid with noise
	  for (int i = 0; i < n; i++) {
	    grid[i] = random(-1, +1);
	  }
	}
	 
	void step() {
	  float[] activator = grid;
	  float[] inhibitor = diffusionRight;
	 
	  for (int level = 0; level < levels - 1; level++) {
	    // blur activator into inhibitor
	    int radius = radii[level];   
	    blur(activator, inhibitor, blurBuffer, nY, nZ, radius);
	 
	    // absdiff between activator and inhibitor
	    for (int i = 0; i < n; i++) {
	      variation[i] = abs(activator[i] - inhibitor[i]);
	    }
	 
	    if (level == 0) {
	      // save bestLevel and bestVariation
	      for (int i = 0; i < n; i++) {
	        bestVariation[i] = variation[i];
	        bestLevel[i] = level;
	        direction[i] = activator[i] > inhibitor[i];
	      }
	      activator = diffusionRight;
	      inhibitor = diffusionLeft;
	    }
	    else {
	      // check/save bestLevel and bestVariation
	      for (int i = 0; i < n; i++) {
	        if (variation[i] < bestVariation[i]) {
	          bestVariation[i] = variation[i];
	          bestLevel[i] = level;
	          direction[i] = activator[i] > inhibitor[i];
	        }
	      }
	      // every level activator=inhibitor from previous
	      float[] swap = activator;
	      activator = inhibitor;
	      inhibitor = swap;
	    }
	    // after this we have the lowest variation in bestvariation
	    // the lowest level in best level
	    // the direction of this diffusion
	  }
	 
	  // update grid from bestLevel
	  // fixed ratio for the change=speed
	  float smallest = Float.POSITIVE_INFINITY;
	  float largest = Float.NEGATIVE_INFINITY;
	  for (int i = 0; i < n; i++) {
	    float curStep = stepSizes[bestLevel[i]];
	    if (direction[i]) {
	      grid[i] += curStep;
	    }
	    else {
	      grid[i] -= curStep;
	    }
	    smallest = min(smallest, grid[i]);
	    largest = max(largest, grid[i]);
	  }
	 
	  // normalize to [-1, +1]
	  float range = (largest - smallest) / 2;
	  for (int i = 0; i < n; i++) {
	    grid[i] = ((grid[i] - smallest) / range) - 1;
	  }
	}
	 
	public void drawBuffer(float[] grid) {
	  buffer.loadPixels();
	  int[] pixels = buffer.pixels;
	  for (int i = 0; i < n; i++) {
	    pixels[i] = color(128 + 128 * grid[i]);
	  }
	  buffer.updatePixels();
	  image(buffer, 0, 0);
	}
	public void start3DFloat(){
		
	}
	int cI=0;

	public void draw() {
		background(0,0,255);
		lights();
		println("frame"+frameCount);
		//scale(3,1,1);
		for (int i = 0; i < 10; i++) {
			step();
		}
		 drawBuffer(grid);

		for (int i = values3D.length - 1; i > grid.length * 2; i--) {
			values3D[i] = values3D[i - grid.length];
		}
		int index=0;
		
			for (int z=0;z<nZ;z++){
				for (int y=0;y<nY;y++){
					
				values3D[(y*nZ+z) + grid.length] = grid[z*nY+y]+1;
				index++;
			}
		}
		for (int i = 0; i < grid.length; i++) {
			
			//values3D[i + grid.length] = grid[i] + 1;
		}
		// borders
		index=0;
		for (int x=0;x<nX;x++){
			for (int y=0;y<nY;y++){
			for (int z=0;z<nZ;z++){
					if (x==0||x==nX-1||y==0||y==nY-1||z==0||z==nZ-1){
						values3D[index]=0;
					}
					index++;
				}
			}
		}
		
		noStroke();
		OpenGLMesh mesh = MCubeEngineLowMemory.initCubesTest(nX, nY, nZ,
				values3D, 1.0f);
		
		mesh.draw((PGraphics3D) g);
		for (int i = 0; i < mesh.xc.length; i++) {
			 //point(mesh.xc[i],mesh.yc[i],mesh.zc[i]);
		}
		if (flagExport){
			mesh.saveObj("turing: "+this.minute());
			flagExport=false;
		}
	}
	 public void keyPressed(){
		 if (key=='s'){
			 flagExport=true;
		 }
	 }
	public void mousePressed() {
	 // setup();
	}
	void blur(float[] from, float[] to, float[] buffer, int w, int h, int radius) {
	  // build integral image
	  for (int y = 0; y < h; y++) {
	    for (int x = 0; x < w; x++) {
	      int i = y * w + x;
	      if (y == 0 && x == 0) {
	        buffer[i] = from[i];
	      } else if (y == 0) {
	        buffer[i] = buffer[i - 1] + from[i];
	      } else if (x == 0) {
	        buffer[i] = buffer[i - w] + from[i];
	      } else {
	        buffer[i] = buffer[i - 1] + buffer[i - w] - buffer[i - w - 1] + from[i];
	      }
	    }
	  }
	  // do lookups
	  for (int y = 0; y < h; y++) {
	    for (int x = 0; x < w; x++) {
	      int minx = max(0, x - radius);
	      int maxx = min(x + radius, w - 1);
	      int miny = max(0, y - radius);
	      int maxy = min(y + radius, h - 1);
	      int area = (maxx - minx) * (maxy - miny);
	       
	      int nw = miny * w + minx;
	      int ne = miny * w + maxx;
	      int sw = maxy * w + minx;
	      int se = maxy * w + maxx;
	       
	      int i = y * w + x;
	      to[i] = (buffer[se] - buffer[sw] - buffer[ne] + buffer[nw]) / area;
	    }
	  }
	}


}
